﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using alib.Enumerable;
using alib.Debugging;
using alib.Dictionary;

namespace agree
{
	public abstract partial class TfsBuilder : TdlParsePos
	{
		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		public _tdl_builder_corefs corefs;

		//[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		//Dictionary<string, XMapTag> xmaptags = new Dictionary<string, XMapTag>();

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Private helper class for use by TdlNavigator
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[DebuggerDisplay("{ToString(),nq}")]
		public class Coreference : List<TmFeatMark>, IAddRangeList<TmFeatMark>
		{
			public Coreference(TdlTok tok)
				: base(10)
			{
				this.tok = tok;
			}
			public TdlTok tok;

			public String tag { get { return GetTag(tok.Text); } }

			public override String ToString()
			{
				return String.Format("{0}  {1}", tag, this.StringJoin(" "));
			}
			public static String GetTag(String i_s)
			{
				switch (i_s[0])
				{
					case '#':
					case '@':
						return i_s.Substring(1);
				}
				return i_s;
			}
		};

		//public class XMapTag : List<TmFeatMark>, IAddRangeList<TmFeatMark>
		//{
		//    public XMapTag(String s)
		//    {
		//        this.s = s;
		//    }

		//    String s;

		//    public override String ToString()
		//    {
		//        return String.Format("{0}  {1}", s, this.StringJoin(" "));
		//    }
		//};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void AddCoref(TdlTok tok, FeatMark fm)
		{
			TmFeatMark tfm = new TmFeatMark(ftm, fm);
			Edge e = tfm.Constraint(this);
			if (e.IsCoreferenced)
			{
				Debug.Print("TDL error: Is feature '{0}' coreferenced more than once for type '{1}' within the same TFS node?\nAction: Ignoring coreference '{2}' in {3}.",
					ftm[fm.i_feat].FEATURE,
					tu.TypeNameOrStringValue(e.FlagsId),
					tok.Text,
					tok.FilePos.ToString());
				return;
			}

			String tag = Coreference.GetTag(tok.Text);
			Coreference ent;
			if (corefs == null)
				corefs = new _tdl_builder_corefs(this);
			else if (corefs.TryGetValue(tag, out ent))
				goto ok;
			corefs.Add(tag, ent = new Coreference(tok));
		ok:
			e = CreateEdge(ftm, e.FlagsId | Edge.Flag.Coreference, e.Mark);
			tfm.SetConstraint(this, e);
			ent.Add(tfm);
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// This needs to be completely overhauled
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public sealed class _tdl_builder_corefs : SlotDictionary<String, Coreference>
		{
			public _tdl_builder_corefs(TfsBuilder tfs) { this.tfs = tfs; }
			TfsBuilder tfs;
			FeatMgr ftm;

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// fixup coreferences
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public void _fixup_corefs(transfer.old.TransferGrammar tg)
			{
				foreach (Coreference xcoref in base.Values)
				{
					if (xcoref.Count < 2)
					{
						var tok = xcoref.tok;
						//throw new TdlException(xcoref.tok, "Coreference tag '#{0}' only used once", xcoref.tag);
						//SysObj.Instance.TransactionStatus(tfs, tok.FilePos, "warning: coreference tag '{0}' only used once", tok.Form);
						continue;
					}

					foreach (var grp in xcoref.GroupBy(x => x.ftm))
					{
						ftm = grp.Key;
						var ltfm = (IList<TmFeatMark>)grp;
						_fixup_coref_set(ltfm);

						Edge e_cur = ltfm[0].Constraint(tfs);
						if (!e_cur.IsCoreferenced)
							e_cur = tfs.CreateEdge(ftm, e_cur.FlagsId | Edge.Flag.Coreference, e_cur.Mark);

						// set the new edge into all the sources
						foreach (TmFeatMark tfm in ltfm)
							tfm.SetConstraint(tfs, e_cur);
					}
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// destructively unify the remaining edges into the starting edge
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			void _fixup_coref_set(IList<TmFeatMark> ltfm)
			{
				TmFeatMark tfm_cur = ltfm[0];
				int i = 0;
				while (true)
				{
					/// initial load or reload after edge below changes
					Edge e_cur = tfm_cur.Constraint(tfs);

					if (++i == ltfm.Count)
						break;

					if (!DestructiveUnifyNoCoreference(tfm_cur, e_cur, ltfm[i].Constraint(tfs)))
						throw new Exception();
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// 
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			bool DestructiveUnifyNoCoreference(TmFeatMark tfm, Edge e1, Edge e2)
			{
				Edge.Flag f1 = e1.FlagsId & ~Edge.Flag.Coreference;
				Edge.Flag f2 = e2.FlagsId & ~Edge.Flag.Coreference;

				//20121013 - loading TMR rules; 'anti_string' 'non_string' etc?
				//if ((f1 | f2).IsStringFamilyType())
				//	throw new Exception();

				Edge.Flag nf;
				if (f2 == 0 || f1 == f2)
					nf = f1;
				else if (f1 == 0)
					nf = f2;
				else if ((nf = ftm.tu.UnifyTypesFull(f1, f2)) < 0)
					return false;

				if (nf == f1)
					return DestructiveUnifyInFeatures(nf, e1.Mark, e2.Mark);

				if (nf == f2)
				{
					tfm.SetConstraint(tfs, e2);
					return DestructiveUnifyInFeatures(nf, e2.Mark, e1.Mark);
				}

				e1 = tfs.CreateEdge(ftm, nf | Edge.Flag.Coreference, e1.Mark);
				if (!DestructiveUnifyInFeatures(nf, e1.Mark, e2.Mark))
					return false;

				tfm.SetConstraint(tfs, e1);
				return true;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// if the destructive target has no edge specified then hijack the other edge into the TFS
			/// by just changing its in-mark. First add a duplicate under the new mark, then remove it below
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			bool DestructiveUnifyInFeatures(Edge.Flag nf, int m1, int m2)
			{
				foreach (int fix in this.ftm.rgrgfix_by_type[(int)(nf & Edge.Flag.MultiIdMask)])
				{
					Edge ne1, ne2;
					if (!tfs.TryGetEdge(fix, m2, out ne2))
						continue;
					if (!tfs.TryGetEdge(fix, m1, out ne1))
						tfs.SetEdge(fix, m1, ne2);
					else if (ne1.Mark != ne2.Mark && !DestructiveUnifyNoCoreference(new TmFeatMark(ftm, fix, m1), ne1, ne2))
						return false;

					/// If the other edge is coreferenced, then we know that something in the coreference table is 
					/// pointing at it, so prior to removing it, we must find those entries and update them.
					if (m2 > m1 /*2012-10-31*/ && ne2.IsCoreferenced)
					{
						foreach (Coreference cr in base.Values)
						{
							for (int i = 0; i < cr.Count; i++)
							{
								TmFeatMark tfm = cr[i];
								if (tfm.ftm != ftm)
									throw new Exception("cross-grammar links are not processed here");

								if (tfm.m == m2)
								{
									/// found a match in the table. check that the host types unify.
									Edge.Flag fmf = tfs.GetCorefEdge(tfm.m).FlagsId;
#if false
									Debug.Print("fmf:{0}  m1:{1}  nf:{2}  {3}",
										Edge.TypeInfoFromFlag(_tm, fmf),
										m1,
										Edge.TypeInfoFromFlag(_tm, nf),
										_tm.g.Name);
#endif
									if (ftm.tu.UnifyTypesFull(fmf, nf) < 0)
										return false;

									/// patch the coreference table to reflect the upcoming change
									tfm.fm.m = m1;
									cr[i] = tfm;
									goto ok;
								}
							}
						}
					}
				ok:
					tfs.RemoveEdge(fix, m2);
				}
				return true;
			}
		};
	};
}